﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using System.Diagnostics;

namespace ParserCovXML
{
    class ParserCovXML
    {
        static void Main(string[] args)
        {

            GenerateReportHelper.GenerateXMLReport(@"D:\002_SVN\ZOPO C5\codebase 2.0.1\trunk\TestResults", @"D:\002_SVN\ZOPO C5\codebase 2.0.1\trunk\APPS\Settings_intermediates");
            //
            //GenerateReportHelper.Serialize(@"D:\cov" + @"\allCovData.bin", allCovData);

            //AllCovData allCovData1 = GenerateReportHelper.unSerialize(@"D:\cov" + @"\allCovData.bin");
            //GenerateReportHelper.GenerateXMLReport(@"D:\cov\TestResults", @"D:\cov\Apps\account_intermediates");

            //AllCovData allCovData = GenerateReportHelper.ParserCovXML(@"D:\cov\coverage.xml");
            //GenerateReportHelper.GenerateJS(allCovData);

            //GenerateReportHelper.GenerateHistoryJS(@"D:\002_SVN\ZOPO C5\codebase 2.0.1\history\Contacts_intermediates");
        }
    }

    /// <summary>
    /// XMLHelper
    /// </summary>
    public class GenerateReportHelper
    {
        static private string emmaPath = @"d:\emma.jar";
        /// <summary>
        /// 调用java -cp emma.java -r xml -in ecFilePath,ecFilePath, 生成xml report
        /// </summary>
        public static string GenerateXMLReport(string ecFilePath, string emFilePath)
        {
            string covFileName = "coverage.xml";
            if (!Directory.Exists(emFilePath))
            {
                throw new ArgumentException("Can not find em file.");
            }

            string[] emFileList = Directory.GetFiles(emFilePath, "*.em", SearchOption.AllDirectories);
            if (emFileList.Length != 1)
            {
                throw new ArgumentException("The count of em file is not 1.");
            }

            if (!Directory.Exists(ecFilePath))
            {
                throw new ArgumentException("Can not find ec file.");
            }

            string[] ecFileList = Directory.GetFiles(ecFilePath, "*.ec", SearchOption.AllDirectories);
            if (ecFileList.Length < 1)
            {
                throw new ArgumentException("Can not find ec file.");
            }

            if (File.Exists(emFilePath + @"\" +covFileName))
            {
                File.Delete(emFilePath + @"\" + covFileName);
            }

            StringBuilder sbCmd = mergeECFiles(ecFileList, emFilePath);
            sbCmd .Append( "\"" + emFileList[0] + "\"");

            string cmd = string.Format(@"java -cp {0} emma report -r xml -in {1}", emmaPath,sbCmd.ToString());
            
            string ret = ExecuteCommand(cmd, emFilePath);

            string[] esFileList = Directory.GetFiles(emFilePath, "*.es", SearchOption.AllDirectories);
            for(int i=0;i<esFileList.Length;i++)
            {
                File.Delete(esFileList[i]);
            }

            if (File.Exists(emFilePath + @"\" + covFileName))
            {
                return emFilePath + @"\" + covFileName;
            }
            return "";
        }

        static private StringBuilder mergeECFiles(string[] ecFiles,string outPath)
        {
            if (ecFiles.Length == 0)
            {
                throw new ArgumentException("Can not find em file.");
            }

            string[] esFileList = Directory.GetFiles(outPath, "*.es", SearchOption.AllDirectories);
            for (int i = 0; i < esFileList.Length; i++)
            {
                File.Delete(esFileList[i]);
            }

            List<string> ecFileList = ecFiles.ToList();
            
            StringBuilder sbcmd = sbcmd = new StringBuilder();
            int index = 0;
            for (int i = 0; i < ecFileList.Count;)
            {
                sbcmd.Append("\"" + ecFileList[i] + "\",");

                if (sbcmd.Length > 7000)
                {
                    string cmd = string.Format(@"java -cp {0} emma merge -in {1} -out coverage_{2}.es", emmaPath, sbcmd.ToString(), index);
                    string output = ExecuteCommand(cmd, outPath);
                    if (!File.Exists(string.Format(@"{0}/coverage_{1}.es", outPath, index)))
                    {
                        throw new Exception("generate es file fail...");
                    }
                    ecFileList.RemoveRange(0, i + 1);
                    ecFileList.Add(string.Format(@"{0}\coverage_{1}.es", outPath, index));
                    sbcmd.Remove(0, sbcmd.Length);
                    index++;
                    i = 0;
                }
                else 
                {
                    i++;
                }
            }

            return sbcmd;
        }

        /// <summary>
        /// 根据传入的xml file 路径，解析xml为对象AllCovData
        /// </summary>
        /// <param name="path"></param>
        /// <returns></returns>
        public static AllCovData ParserCovXML(string path, string appName = "", string svnRevision ="")
        {

            XmlDocument doc = new XmlDocument();
            doc.Load(path);

            XmlNode node = doc.SelectSingleNode("/report/data/all");

            AllCovData allCovData = new AllCovData(doc);
            allCovData.SVNRevision = svnRevision;
            allCovData.AppName = appName;
            return allCovData;
        }

        /// <summary>
        /// 序列化 AllCovData 类
        /// </summary>
        /// <param name="fileName"></param>
        /// <param name="allCovData"></param>
        /// <returns></returns>
        public static bool Serialize(string fileName, AllCovData allCovData)
        {
            Stream fStream = null;
            try
            {
                File.Delete(fileName);
                fStream = new FileStream(fileName, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None);
                BinaryFormatter binFormat = new BinaryFormatter();
                binFormat.Serialize(fStream, allCovData);
            }

            catch (IOException e)
            {
                System.Console.Write(e.Message);
            }
            finally
            {
                if (fStream != null)
                {
                    fStream.Close();
                }
            }
            return true;
        }

        /// <summary>
        /// 反序列化 AllCovData 类
        /// </summary>
        /// <param name="fileName"></param>
        /// <returns></returns>
        public static AllCovData unSerialize(string fileName)
        {
            Stream fStream = null;
            AllCovData allCovData = null;
            try
            {
                BinaryFormatter binFormat = new BinaryFormatter();
                fStream = new FileStream(fileName, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None);
                allCovData = (AllCovData)binFormat.Deserialize(fStream);
            }
            catch (IOException e)
            {
                System.Console.Write(e.Message);
            }
            finally
            {
                if (fStream != null)
                {
                    fStream.Close();
                }
            }
            return allCovData;
        }

        public static string ExecuteCommand(string commandText, string workingDirectory)
        {
            Process p = new Process();
            p.StartInfo.FileName = "cmd.exe";
           
            p.StartInfo.UseShellExecute = false;
            p.StartInfo.RedirectStandardInput = true;
            p.StartInfo.RedirectStandardOutput = true;
            p.StartInfo.RedirectStandardError = true;
            p.StartInfo.CreateNoWindow = true;
            p.StartInfo.WorkingDirectory = workingDirectory;
            p.StartInfo.Arguments = "/c " + commandText;
            string strOutput = null;
            try
            {
                p.Start();
                //StreamWriter utf8Writer = new StreamWriter(p.StandardInput.BaseStream, Encoding.UTF8);
                //utf8Writer.WriteLine(commandText);
                //utf8Writer.WriteLine("exit");
                //utf8Writer.Close();
                //byte[] buffer = System.Text.Encoding.Default.GetBytes(commandText);
                //p.StandardInput.BaseStream.Write(buffer, 0, buffer.Length);
                //p.StandardInput.WriteLine();
                //p.StandardInput.WriteLine("exit");
                strOutput = p.StandardOutput.ReadToEnd();
               
                p.WaitForExit();
                p.Close();
            }
            catch (Exception e)
            {
                strOutput = e.Message;
            }
            return strOutput;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="allCovData"></param>
        /// <returns></returns>
        public static string GenerateJS(AllCovData allCovData)
        {
            //['Africa', 'America', 'Asia', 'Europe', 'Oceania']
            //StreamReader sr = new StreamReader(@"D:\temp.txt");
            //string text = sr.ReadToEnd();
            //sr.Close();
            string template = "$(function () {\n        $(\"#chartcontainer\").css(\"height\",\"#height#px\")\n        $('#chartcontainer').highcharts({\n            chart: {\n                type: 'bar'\n            },\n            title: {\n                text: 'Android code coverage'\n            },\n            subtitle: {\n                text: 'Source: #AppName#'\n            },\n            xAxis: {\n                categories: #categories#,\n                title: {\n                    text: null\n                }\n            },\n            yAxis: {\n                min: 0,\n                title: {\n                    text: 'Coverage (%)',\n                    align: 'high'\n                },\n                labels: {\n                    overflow: 'justify'\n                }\n            },\n            tooltip: {\n                valueSuffix: ' %'\n            },\n            plotOptions: {\n                bar: {\n                    dataLabels: {\n                        enabled: true\n                    }\n                }\n            },\n            legend: {\n                layout: 'vertical',\n                align: 'right',\n                verticalAlign: 'top',\n                x: -100,\n                y: 100,\n                floating: true,\n                borderWidth: 1,\n                backgroundColor: '#FFFFFF',\n                shadow: true\n            },\n            credits: {\n                enabled: false\n            },\n            series: [{\n                #classCov#\n            }, {\n                #methodCov#\n            }, {\n                #blockCov#\n            }#lineCov#\n]\n        });\n    });";
          

            string categories = "[";
            string classCov = "name: 'Class Coverage',data: [";
            string methodCov = "name: 'Method Coverage',data: [";
            string blockCov = "name: 'Block Coverage',data: [";
            string lineCov = ",{name: 'Line Coverage',data: [";
            string sep = ",";

            // add total coverage info
            categories += "'" + "Total" + "'" + sep;
            classCov += string.Format("{0:F}", 100 * allCovData.AllCov.ClassCov.Value.Covered / allCovData.AllCov.ClassCov.Value.Total) + sep;
            methodCov += string.Format("{0:F}", 100 * allCovData.AllCov.MethodCov.Value.Covered / allCovData.AllCov.MethodCov.Value.Total) + sep;
            blockCov += string.Format("{0:F}", 100 * allCovData.AllCov.BlockCov.Value.Covered / allCovData.AllCov.BlockCov.Value.Total) + sep;
            if (allCovData.HasLineInfo)
            {
                lineCov += string.Format("{0:F}", 100 * allCovData.AllCov.LineCov.Value.Covered / allCovData.AllCov.LineCov.Value.Total) + sep;
            }

            // add package coverage info
            for(int i =0  ;i< allCovData.PkgCov.Count;i++)
            {
                CovDate covData = allCovData.PkgCov[i];
                if (i == allCovData.PkgCov.Count - 1)
                {

                    sep = "]";
                }
                categories += "'" + covData.Name +"'" +sep;
                classCov += string.Format("{0:F}",100*covData.ClassCov.Value.Covered / covData.ClassCov.Value.Total) + sep;
                methodCov += string.Format("{0:F}",100*covData.MethodCov.Value.Covered / covData.MethodCov.Value.Total) + sep;
                blockCov += string.Format("{0:F}", 100 * covData.BlockCov.Value.Covered / covData.BlockCov.Value.Total) + sep;
                if (allCovData.HasLineInfo)
                {
                    lineCov += string.Format("{0:F}", 100 * covData.LineCov.Value.Covered / covData.LineCov.Value.Total) + sep;
                }
            }

            template = template.Replace("#categories#", categories);
            template = template.Replace("#classCov#", classCov);
            template = template.Replace("#methodCov#", methodCov);
            template = template.Replace("#blockCov#", blockCov);
            if (allCovData.HasLineInfo)
            {
                template = template.Replace("#lineCov#", lineCov + "}");
            }
            else 
            {
                template = template.Replace("#lineCov#", "");
            }
            template = template.Replace("#AppName#", allCovData.AppName);
            int height = (allCovData.PkgCov.Count + 1) * 80; // +1 表示加上了total cov 高度
            template = template.Replace("#height#", (height > 400 ? height : 400).ToString());
            return template;
        }

        /// <summary>
        /// 获取historyPath下响应packageName的覆盖率
        /// </summary>
        /// <param name="tagPath"></param>
        /// <param name="packageName"></param>
        /// <returns></returns>
        public static string GenerateHistoryJS(string historyPath)
        {
            string template = "$(function () {$(\"#chartcontainer\").css(\"height\",\"400px\")\n     \r\n        $('#chartcontainer').highcharts({\r\n            chart: {\r\n                type: 'line',\r\n                marginRight: 130,\r\n                marginBottom: 25\r\n            },\r\n            title: {\r\n                text: 'History Coverage',\r\n                x: -20 //center\r\n            },\r\n            subtitle: {\r\n                text: 'Source: #APPNAME#',\r\n                x: -20\r\n            },\r\n            xAxis: {\r\n                categories: [#categories#]\r\n            },\r\n            yAxis: {\r\n                title: {\r\n                    text: 'Coverage (%)'\r\n                },\r\n                plotLines: [{\r\n                    value: 0,\r\n                    width: 1,\r\n                    color: '#808080'\r\n                }]\r\n            },\r\n            tooltip: {\r\n                valueSuffix: '%'\r\n            },\r\n            legend: {\r\n                layout: 'vertical',\r\n                align: 'right',\r\n                verticalAlign: 'top',\r\n                x: -10,\r\n                y: 100,\r\n                borderWidth: 0\r\n            },\r\n            series: [#classCov#, #methodCov#, #blockCov# #lineCov#]\r\n        });\r\n    });";
            string[] sumList = Directory.GetFiles(historyPath, "*.sum", SearchOption.AllDirectories);
            if(sumList.Length<1)
            {
                 throw new ArgumentException("Can not find *.sum file."); 
            }
            SortedList<string,AllCovData> allCovDataList = new SortedList<string, AllCovData>();
            //D:\002_SVN\ZOPO C5\codebase 2.0.1\history\Contacts_intermediates\1309091706.sum
            for (int i = 0; i < sumList.Length; i++)
            {

                AllCovData allCovData = unSerialize(sumList[i]);

                string tagTime;
                try
                {
                    tagTime =sumList[i].Substring(sumList[i].IndexOf(@".sum") - 10, 10);
                }
                catch (FormatException)
                {
                    Console.WriteLine("{0} is not in the correct format.", sumList[i]);
                    continue;
                }
                allCovDataList.Add(tagTime, allCovData);
            }

            string categories = "";
            string classCov = "{name: 'Class Coverage',data: [";
            string methodCov = "{name: 'Method Coverage',data: [";
            string blockCov = "{name: 'Block Coverage',data: [";
            string lineCov = ",{name: 'Line Coverage',data: [";
            string sep = ",";


            for (int i = 0; i < allCovDataList.Count; i++)
            {
                categories += "'" + allCovDataList.Keys[i]+"'" + sep;
                 
                classCov += string.Format("{0:F}",100*allCovDataList.Values[i].AllCov.ClassCov.Value.Covered / allCovDataList.Values[i].AllCov.ClassCov.Value.Total) + sep;
                methodCov += string.Format("{0:F}", 100 * allCovDataList.Values[i].AllCov.MethodCov.Value.Covered / allCovDataList.Values[i].AllCov.MethodCov.Value.Total) + sep;
                blockCov += string.Format("{0:F}", 100 * allCovDataList.Values[i].AllCov.BlockCov.Value.Covered / allCovDataList.Values[i].AllCov.BlockCov.Value.Total) + sep;
                if (allCovDataList.Values[i].HasLineInfo)
                {
                    lineCov += string.Format("{0:F}", 100 * allCovDataList.Values[i].AllCov.LineCov.Value.Covered / allCovDataList.Values[i].AllCov.LineCov.Value.Total) + sep;
                }
            }

            classCov += "]}";
            methodCov += "]}";
            blockCov += "]}";
            lineCov += "]}";

            template = template.Replace("#APPNAME#", allCovDataList.Values[0].AppName);

            template = template.Replace("#categories#", categories);
            template = template.Replace("#classCov#", classCov);
            template = template.Replace("#methodCov#", methodCov);
            template = template.Replace("#blockCov#", blockCov);
            if (allCovDataList.Values[0].HasLineInfo)
            {
                template = template.Replace("#lineCov#", lineCov);
            }
            else 
            {
                template = template.Replace("#lineCov#", "");
            }

            return template;
        }
    }

    /// <summary>
    /// <all name="all classes">
    /// </summary>
    [Serializable]
    public class AllCovData
    {
        public string SVNRevision = "";
        public string AppName = "";
        public CovDate AllCov;
        public List<CovDate> PkgCov = new List<CovDate>();
        public bool HasLineInfo;

        public AllCovData(XmlNode covDate)
        {
            HasLineInfo = (null != covDate.SelectSingleNode("/report/data/all/coverage[@type='line, %']"));

            this.AllCov = new CovDate(covDate.SelectSingleNode("/report/data/all"),this.HasLineInfo);

            XmlNodeList pkgCovNodeList = covDate.SelectNodes("/report/data/all/package");

            foreach (XmlNode node in pkgCovNodeList)
            {

                CovDate pkgCov = new CovDate(node, this.HasLineInfo);
                PkgCov.Add(pkgCov);
            }
        }
    }

    /// <summary>
    /// List of <package name="com.sun.tools.javac.v8.resources">
    /// </summary>
    [Serializable]
    public class CovDate
    {
        public string Name;
        public CovItem ClassCov;
        public CovItem MethodCov;
        public CovItem BlockCov;
        public CovItem LineCov;

        public CovDate(XmlNode covDate, bool hasLineInfo)
        {
            this.Name = covDate.Attributes["name"].Value;
            this.ClassCov = new CovItem(covDate.SelectSingleNode("coverage[@type='class, %']"));
            this.MethodCov = new CovItem(covDate.SelectSingleNode("coverage[@type='method, %']"));
            this.BlockCov = new CovItem(covDate.SelectSingleNode("coverage[@type='block, %']"));

            if (hasLineInfo)
            { 
                this.LineCov = new CovItem(covDate.SelectSingleNode("coverage[@type='line, %']"));
            }
          
        }
    }

    /// <summary>
    ///  <coverage type="class, %" value="100% (185/185)"/>
    /// </summary>
    [Serializable]
    public class CovItem
    {
        public string Type;
        public CovRate Value;

        public CovItem(XmlNode covItem)
        {
            this.Type = covItem.Attributes["type"].Value;
            this.Value = new CovRate(covItem.Attributes["value"].Value);
        }

    }

    /// <summary>
    /// value="100% (185/185)
    /// </summary>
    [Serializable]
    public class CovRate
    {
        public string CoverdRate;
        public float Covered;
        public float Total;

        public CovRate(string value)
        {
            string[] strArray = value.Split(' ');
            string[] strArrayLeft = strArray[strArray.Length - 1].Split('/');
            CoverdRate = strArray[0];

            Covered = float.Parse(strArrayLeft[0].Substring(1));
            Total = float.Parse(strArrayLeft[1].Substring(0, strArrayLeft[1].Length - 1));
        }

        public string ToString()
        {
            string ret = string.Format("CoverdRate:{0},Covered:{1},Total:{2}", CoverdRate, Covered, Total);

            return ret;
        }
    }
}
